/* SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 * SPDX-FileCopyrightText: Copyright 2015 Micron Technology, Inc.
 */

#ifndef HSE_ERROR_MERR_H
#define HSE_ERROR_MERR_H

/*
 * The merr_t typedef is designed to enable developers to identify the line
 * of code that first throws an error without having debug printfs up and down
 * the call stack.  If a stack of functions use merr_t as a return type,
 * and each layer propagates merr_t values up the stack, then the caller at
 * the top can log an error with information to help identify the code that
 * first threw the error.
 *
 * For convenience and simplicity a merr_t is a 64 bit integer that uniquely
 * identifies the file, line, and errno of the call site where the merr_t
 * was generated.  If we use a full 32 bits for the errno and 14 bits for
 * the line number we are left with only 18 bits into which to save the file
 * name.  We cannot easily store the pointer to the file name within 18 bits,
 * but we can store the difference between the file name and a well-known
 * symbol that is located nearby.
 *
 * To that end, each file has a private symbol named "_merr_file" and there
 * exists one global symbol named "merr_base" from which the difference is
 * obtained.  All these symbols are placed in the same section by the
 * linker, so the relative difference between any two of these symbols
 * is small enough to easly fit within 18 bits.
 *
 * The only way to construct a valid merr_t is either by assignment to zero,
 * generation via merr() (e.g., err = merr(EINVAL)), or by direct assignment
 * from a merr_t generated by one of the first two  methods.  All other
 * constructions are invalid and will cause 'merr_file(err)' to generate a
 * string of the form "merr_bug[0-3][uk]', where the final character denotes
 * whether the call to merr_file() was performed in user space or the kernel.
 * Invalid merr_t can be extremely difficult to trace to their point of origin
 * due to the fact that they likely contain no valid call site details. If
 * you're lucky, the line number is valid and you can use the following find
 * command to locate the origin of the error:
 *
 *     find . -name \*.[ch] -print | xargs grep -n 'merr' | grep :120:
 *
 *     foo.c:120:  return merr(ENOSPC);
 */

#include <stddef.h>

#include <hse/types.h>

#include <hse/util/compiler.h>

#define EBUG (991)

/* Alignment of _hse_merr_file in section "hse_merr"
 */
#define MERR_ALIGN (1 << 6)

#define _merr_section __attribute__((section("hse_merr")))
#define _merr_attributes                  \
    _merr_section HSE_ALIGNED(MERR_ALIGN) \
    HSE_MAYBE_UNUSED

static char _hse_merr_file[MERR_ALIGN] _merr_attributes = __BASE_FILE__;

extern char hse_merr_base[];
extern char hse_merr_bug0[];
extern char hse_merr_bug1[];
extern char hse_merr_bug2[];

/* Layout of merr_t:
 *
 *   Field   #bits  Description
 *   ------  -----  ----------
 *   63..48   16    signed offset of (_he_merr_file - merr_base) / MERR_ALIGN
 *   47..32   16    line number
 *   31..16   16    error context
 *   15..0    16    positive errno value
 */
#define MERR_FILE_SHIFT (48)
#define MERR_LINE_SHIFT (32)
#define MERR_CTX_SHIFT  (16)

#define MERR_FILE_MASK  (0xffff000000000000ul)
#define MERR_LINE_MASK  (0x0000ffff00000000ul)
#define MERR_CTX_MASK   (0x00000000ffff0000ul)
#define MERR_ERRNO_MASK (0x000000000000fffful)

typedef hse_err_t merr_t;
typedef const char *
merr_stringify(unsigned int);

/**
 * merr() - Pack given errno and call-site info into a merr_t
 */
#define merr(_errnum) merr_pack((_errnum), 0, _hse_merr_file, __LINE__)

/**
 * merrx() - Pack given errno, error context, and call-site info into a merr_t
 */
#define merrx(_errnum, _ctx) merr_pack((_errnum), (_ctx), _hse_merr_file, __LINE__)

/* Not a public API, called only via the merr() macro.
 */
merr_t
merr_pack(int error, unsigned int ctx, const char *file, int line);

/**
 * merr_strerror() - Format errno description from merr_t
 */
size_t
merr_strerror(merr_t err, char *buf, size_t buf_sz);

/**
 * merr_strinfo() - Format file, line, ctx, and errno from merr_t
 */
char *
merr_strinfo(merr_t err, char *buf, size_t buf_sz, merr_stringify ctx_stringify, size_t *need_sz);

/**
 * merr_file() - Return file name ptr from merr_t
 */
const char *
merr_file(merr_t err);

/**
 * merr_errno() - Return the errno from given merr_t
 */
static HSE_ALWAYS_INLINE int
merr_errno(merr_t err)
{
    return err & MERR_ERRNO_MASK;
}

/**
 * merr_ctx() - Return the error context from given merr_t
 */
static HSE_ALWAYS_INLINE unsigned int
merr_ctx(merr_t err)
{
    return (unsigned int)((err & MERR_CTX_MASK) >> MERR_CTX_SHIFT);
}

/**
 * merr_lineno() - Return the line number from given merr_t
 */
static HSE_ALWAYS_INLINE int
merr_lineno(merr_t err)
{
    return (int)((err & MERR_LINE_MASK) >> MERR_LINE_SHIFT);
}

#endif
